{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "3eb044a0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a02f6303",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
     ]
    }
   ],
   "source": [
    "import bs4\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "# 加载网页数据 例如网页 https://www.chongwj.com/siyang/239.html\n",
    "web_loader = WebBaseLoader(\n",
    "    web_paths=[\"https://www.chongwj.com/siyang/239.html\"],\n",
    "    bs_kwargs=dict(\n",
    "        # 只解析指定的标签, 类选择器\n",
    "        parse_only=bs4.SoupStrainer(class_=(\"post-239 post type-post status-publish format-standard has-post-thumbnail hentry category-siyang tag-6 tag-34\"))\n",
    "    )\n",
    ")\n",
    "\n",
    "docs = web_loader.load()\n",
    "# print(f\"文档数: {len(docs)}\")\n",
    "# print(docs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0b150a4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 文本分割\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)\n",
    "\n",
    "splits = text_splitter.split_text(docs[0].page_content)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44b1b8d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 存储\n",
    "from langchain_chroma import Chroma\n",
    "from langchain.embeddings import HuggingFaceBgeEmbeddings\n",
    "\n",
    "embeddings = HuggingFaceBgeEmbeddings(\n",
    "    model_name=r\"D:\\modelscope\\cache\\bge-m3\",\n",
    "    model_kwargs={\"device\": \"cpu\"},\n",
    "\n",
    ")\n",
    "\n",
    "vectorstore = Chroma.from_texts(\n",
    "    splits,\n",
    "    embeddings\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "19151c98",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 检索器\n",
    "retriever = vectorstore.as_retriever()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7a2a65c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 语言模型\n",
    "from langchain_deepseek import ChatDeepSeek\n",
    "\n",
    "llm = ChatDeepSeek(model=\"deepseek-chat\", temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "485da007",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个问题模板\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "system_template = \"\"\"\n",
    "你是一个回答问题的助手，使用提供的信息回答问题。\n",
    "如果你不确定答案，请说你不知道，不要编造答案。\n",
    "使用几个句子回答问题，保证回答的简洁易懂。\n",
    "\n",
    "{context}\n",
    "\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", system_template),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1485b2e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "猫薄荷，又称樟脑草或荆芥，是一种植物，其气味对猫有特殊吸引力。它含有一种叫做荆芥内酯的化合物，能刺激猫的嗅觉受体，引发短暂而强烈的兴奋反应，如打滚、蹭地或过度活跃。这种效果通常持续5-15分钟，且对猫无害，但并非所有猫都会对其产生反应。猫薄荷常被用于玩具或零食中，以丰富猫的环境和减轻压力。\n"
     ]
    }
   ],
   "source": [
    "# 得到chain 无历史记录\n",
    "from langchain.chains.combine_documents import create_stuff_documents_chain\n",
    "from langchain.chains import create_retrieval_chain\n",
    "\n",
    "chain1 = create_stuff_documents_chain(llm, prompt)\n",
    "\n",
    "chain2 = create_retrieval_chain(retriever, chain1)\n",
    "\n",
    "resp = chain2.invoke({\"input\": \"什么是猫薄荷？\"})\n",
    "\n",
    "print(resp[\"answer\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "7b64bdf0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 有历史记录版本，打开上面的MessagesPlaceholder(\"chat_history\") 的注释\n",
    "from langchain.chains import create_history_aware_retriever, create_retrieval_chain\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain.chains.combine_documents import create_stuff_documents_chain\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "contextualize_q_system_prompt = \"\"\"\n",
    "给你一个聊天历史记录和一个可能需要引用这个聊天历史记录内容的用户问题，\n",
    "请你根据聊天历史记录，生成一个上下文相关的问题。\n",
    "如果聊天历史记录中没有相关内容，请直接使用用户的问题。\n",
    "\"\"\"\n",
    "\n",
    "retriever_history_temp = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", contextualize_q_system_prompt),\n",
    "        MessagesPlaceholder(\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 创建一个 子链\n",
    "history_chain = create_history_aware_retriever(llm, retriever, retriever_history_temp)\n",
    "\n",
    "# 保存历史记录\n",
    "store = {}\n",
    "\n",
    "def get_session_history(session_id: str):\n",
    "    if session_id not in store:\n",
    "        store[session_id] = ChatMessageHistory()\n",
    "    return store[session_id]\n",
    "\n",
    "\n",
    "llm_chain = create_stuff_documents_chain(llm, prompt)\n",
    "\n",
    "chain = create_retrieval_chain(history_chain, llm_chain)\n",
    "\n",
    "result_chain = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    history_messages_key=\"chat_history\",\n",
    "    output_messages_key=\"answer\"\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "20cec007",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "猫薄荷，学名荆芥或樟脑草，是一种草本植物，其气味对猫有特殊吸引力。它含有一种名为荆芥内酯的化合物，能刺激猫的嗅觉系统，引发短暂而强烈的兴奋反应，如打滚、蹭地或过度活跃，但不会对猫造成伤害或成瘾。这种反应通常持续约10-15分钟，之后猫会恢复正常。猫薄荷常被用于玩具或零食中，以丰富猫的环境和减轻压力。\n"
     ]
    }
   ],
   "source": [
    "resp1 = result_chain.invoke(\n",
    "    {\"input\": \"什么是猫薄荷？\"},\n",
    "    config={\"configurable\": {\"session_id\": \"test1\"}}\n",
    ")\n",
    "\n",
    "print(resp1[\"answer\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2165dfdc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "是的，有些猫确实不喜欢猫薄荷。大约有30%的猫对猫薄荷无感，既不会兴奋也没有负面反应。这可能与遗传因素有关，因为猫对猫薄荷的反应是由特定基因决定的。如果猫不喜欢猫薄荷，可以尝试用忍冬花（金银花）替代，但仍有少数猫对两者都不感兴趣。\n"
     ]
    }
   ],
   "source": [
    "resp2 = result_chain.invoke(\n",
    "    {\"input\": \"会有猫讨厌它吗？\"},\n",
    "    config={\"configurable\": {\"session_id\": \"test1\"}}\n",
    ")\n",
    "\n",
    "print(resp2[\"answer\"])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain_learn",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
